#include "Grid.h"

#include <vector>

#include "Vertex.h"

namespace 
{
	float getHeight(const float x, const float z)
	{
		return 0.3f * (z * sinf(0.1f * x) + x * cosf(0.1f * z));
	}
}

namespace D3D10Utils
{
	Grid::Grid(const UINT rows, const UINT columns)
		: md3dDevice(0)
		, mVertexBuffer(0)
		, mIndexBuffer(0)
		, mNumVertices(rows * columns)
		, mNumFaces((rows - 1) * (columns - 1) * 2)
		, mRows(rows)
		, mColumns(columns)
	{

	}

	Grid::~Grid()
	{
		ReleaseCOM(mVertexBuffer);
		ReleaseCOM(mIndexBuffer);
	}

	void Grid::init(ID3D10Device * const device, const float cellSpacingX, 
		const float cellSpacingZ)
	{
		if (!device)
		{
			MessageBox(0, L"NULL pointer", 0, 0);
			PostQuitMessage(0);
		}

		md3dDevice = device;

		// Compute the half width and height of the grid.		
		// Our grid will be at xz plane, that the reason why we ask for cell
		// spacing in that coordinates.
		const float halfWidth = (mColumns - 1) * cellSpacingX * 0.5f;
		const float halfHeight = (mRows - 1) * cellSpacingZ * 0.5f;
		
		// Create vertices:
		// Vij = (-0.5 * width + i * cellSpacingX, 0.5 * height - j * cellSpacingZ)
		Vertex *vertices = new Vertex[mNumVertices];
		for(UINT j = 0; j < mColumns; ++j)
		{
			const float x = -halfWidth + j * cellSpacingX;
			
			for(UINT i = 0; i < mRows; ++i)
			{
				const float z = halfHeight - i * cellSpacingZ;
				const float y = getHeight(x, z);

				// Update the vertex
				const UINT vertexIndex = i * mRows + j;
				vertices[vertexIndex].pos = D3DXVECTOR3(x, y, z);

				// Color the vertex base on its height.
				if (y < -10.0f)
					vertices[vertexIndex].color = BEACH_SAND;
				else if (y < 5.0f)
					vertices[vertexIndex].color = LIGHT_YELLOW_GREEN;
				else if (y < 12.0f)
					vertices[vertexIndex].color = DARK_YELLOW_GREEN;
				else if (y < 20.0f)
					vertices[vertexIndex].color = DARKBROWN;
				else
					vertices[vertexIndex].color = WHITE;
			}
		}

		// Description of the vertex buffer.
		D3D10_BUFFER_DESC vertexBufferDescription;
		vertexBufferDescription.ByteWidth = sizeof(Vertex) * mNumVertices;

		// A resource that can only be read by the GPU. 
		// It cannot be written by the GPU, and cannot be accessed at all by the CPU. 
		// This type of resource must be initialized when it is created, 
		// since it cannot be changed after creation.
		vertexBufferDescription.Usage = D3D10_USAGE_IMMUTABLE; 

		// Bind a buffer as a vertex buffer to the input-assembler stage.
		vertexBufferDescription.BindFlags = D3D10_BIND_VERTEX_BUFFER; 
		vertexBufferDescription.CPUAccessFlags = 0; // No CPU access necessary.
		vertexBufferDescription.MiscFlags = 0; 	
		// Specifies data for initializing a subresource.
		D3D10_SUBRESOURCE_DATA vertexBufferData;
		vertexBufferData.pSysMem = vertices;

		ErrorHandler(md3dDevice->CreateBuffer(&vertexBufferDescription, &vertexBufferData, &mVertexBuffer));

		delete[] vertices;

		// Create indices.
		DWORD *indices = new DWORD[mNumFaces * 3];
		UINT quadOffset = 0;
		for(UINT j = 0; j < mColumns - 1; ++j)
		{
			for(UINT i = 0; i < mRows - 1; ++i)
			{
				// Define the needed vertices.
				const DWORD upperLeft = i * mRows + j;
				const DWORD upperRight = i * mRows + j + 1;
				const DWORD bottomLeft = (i + 1) * mRows + j;
				const DWORD bottomRight = (i + 1) * mRows + j + 1;

				indices[quadOffset] = upperLeft;
				indices[quadOffset + 1] = upperRight;
				indices[quadOffset + 2] = bottomLeft;

				indices[quadOffset + 3] = bottomLeft;
				indices[quadOffset + 4] = upperRight;
				indices[quadOffset + 5] = bottomRight;

				// Next quad
				quadOffset += 6; 
			}
		}

		// Description of the index buffer.
		D3D10_BUFFER_DESC indexBufferDescription;
		indexBufferDescription.ByteWidth = sizeof(DWORD) * mNumFaces * 3;

		// A resource that can only be read by the GPU. 
		// It cannot be written by the GPU, and cannot be accessed at all by the CPU. 
		// This type of resource must be initialized when it is created, 
		// since it cannot be changed after creation.
		indexBufferDescription.Usage = D3D10_USAGE_IMMUTABLE;

		// Bind a buffer as a index buffer to the input-assembler stage.
		indexBufferDescription.BindFlags = D3D10_BIND_INDEX_BUFFER;
		indexBufferDescription.CPUAccessFlags = 0; // No CPU access necessary.
		indexBufferDescription.MiscFlags = 0;

		D3D10_SUBRESOURCE_DATA indexBufferData;
		indexBufferData.pSysMem = indices;

		ErrorHandler(md3dDevice->CreateBuffer(&indexBufferDescription, &indexBufferData, &mIndexBuffer));
		
		delete[] indices;
	}

	void Grid::draw()
	{
		const UINT stride = sizeof(Vertex);
		const UINT offset = 0;
		md3dDevice->IASetVertexBuffers(0, 1, &mVertexBuffer, &stride, &offset);
		md3dDevice->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_R32_UINT, 0);

		md3dDevice->DrawIndexed(mNumFaces * 3, 0, 0);
	}
}
